業務アプリの現場では、 「ローカルでサクサク動く」×「クラウドでデータ共有」 を両立したいケースが増えています。 そのときに最も現実的なのが、ローカルSQLite × クラウドDBのハイブリッド構成です。
この記事では、ローカルDBとクラウドDBを同期させるための 設計パターン・差分同期・衝突解決・オフライン対応・API設計を 実務目線で整理します。
・ローカルDB × クラウド同期の全体構成
・差分同期(UpdatedAt / Version)戦略
・Push/Pull同期のAPI設計
・衝突解決(Conflict Resolution)の考え方
・オフライン対応と再同期
・業務アプリ向けベストプラクティス
1. ローカルDB × クラウド同期の全体像
まずは、よくあるハイブリッド構成の全体像から。
■ 典型構成
- クライアント:WPF / WinUI / MAUI / Web
- ローカルDB:SQLite(オフライン・高速レスポンス用)
- APIサーバー:ASP.NET Core / 他
- クラウドDB:SQL Server / PostgreSQL / MySQL など
クライアントは基本的にローカルSQLiteを参照し、 一定間隔または操作タイミングでAPI経由でクラウドDBと同期します。
2. 同期の基本コンセプト:Push と Pull
ローカルDBとクラウドDBの同期は、 Push(ローカル → クラウド) と Pull(クラウド → ローカル) に分けて考えます。
■ Pull(クラウド → ローカル)
- クラウド側で更新されたデータをローカルに反映
- UpdatedAt / Version を使って差分取得
■ Push(ローカル → クラウド)
- ローカルで追加・更新・削除されたデータをクラウドに送信
- 送信キュー(アウトボックス)を持つと安定する
この2つを明確に分けて設計することで、 同期ロジックがシンプルになります。
3. 差分同期の設計(UpdatedAt / Version 戦略)
全件同期は非現実的なので、 差分だけを同期する仕組みが必要です。
■ UpdatedAt方式
- 各レコードに UpdatedAt(最終更新日時)を持たせる
- 前回同期時刻以降に更新されたデータだけ取得
CREATE TABLE Users (
Id TEXT PRIMARY KEY,
Name TEXT NOT NULL,
Email TEXT,
UpdatedAt TEXT NOT NULL
);
■ Pull APIの例
GET /api/users/sync?since=2026-04-01T00:00:00Z
■ Version方式
- 整数のVersionを持たせる
- 更新のたびに Version++
- sinceVersion 以降を取得
どちらでもよいですが、UpdatedAtの方が人間にとって分かりやすいです。
4. Push同期の設計(アウトボックスパターン)
ローカルでの変更をクラウドに送るときは、 「送信キュー(アウトボックス)」を持つと安定します。
■ ローカル側アウトボックステーブル例
CREATE TABLE SyncOutbox (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
EntityName TEXT NOT NULL,
EntityId TEXT NOT NULL,
Operation TEXT NOT NULL, -- Insert / Update / Delete
PayloadJson TEXT NOT NULL,
CreatedAt TEXT NOT NULL,
SentAt TEXT
);
アプリは、実データ更新と同時に SyncOutboxに「変更内容」を記録します。 ネットワークが復帰したタイミングで、 まとめてAPIに送信します。
■ Push APIの例
POST /api/users/sync
[
{
"Operation": "Update",
"EntityId": "U001",
"Name": "山田太郎",
"Email": "taro@example.com",
"UpdatedAt": "2026-04-05T10:00:00Z"
}
]
5. 衝突解決(Conflict Resolution)の考え方
ローカルとクラウドで同じレコードが別々に更新されると、 「どちらを採用するか?」という問題が発生します。
■ よくある戦略
- クラウド優先(Server Wins)
- ローカル優先(Client Wins)
- UpdatedAtが新しい方を採用
- 両方保持して「要確認」フラグを立てる
業務アプリでは、 「UpdatedAtが新しい方」+「重要データは要確認」 の組み合わせが現実的です。
■ サーバー側での判定イメージ
// 受信した更新 vs サーバー上の既存データ
if (incoming.UpdatedAt >= current.UpdatedAt)
{
// 上書き
}
else
{
// 衝突としてログに残す or 別テーブルに退避
}
6. オフライン対応と再同期
ローカルDB × クラウド同期の最大のメリットは、 オフラインでも業務が継続できることです。
■ オフライン時の動き
- 読み取り:ローカルSQLiteから取得
- 書き込み:ローカルSQLiteに保存+SyncOutboxに記録
■ オンライン復帰時の流れ
- Push:SyncOutboxの未送信データをAPIに送信
- Pull:クラウド側の差分をローカルに反映
- SyncOutboxのSentAtを更新
この「Push → Pull」の順番を守ることで、 ローカルの変更がクラウドに反映されてから最新状態を取得できます。
7. API設計のポイント
同期用APIは、通常のCRUD APIとは少し設計が異なります。
■ Pull API
GET /api/users/sync?since=2026-04-01T00:00:00Z
- 差分のみ返す
- Deletedフラグ付きレコードも返す(論理削除)
■ Push API
POST /api/users/sync
[
{ "Operation": "Insert", ... },
{ "Operation": "Update", ... },
{ "Operation": "Delete", ... }
]
- バッチで受け取る
- 1件ずつ結果を返す(成功/失敗)
8. ID設計(GUID / サロゲートキー)
ローカルとクラウドで同じテーブルを扱う場合、 IDの重複を避ける必要があります。
■ GUID(文字列ID)
- クライアント側で一意なIDを生成できる
- オフラインでもID発行可能
Id TEXT PRIMARY KEY
整数のオートインクリメントより、GUIDの方が同期には向いています。
9. 業務アプリ向けベストプラクティス
- ローカルはSQLite、クラウドはSQL Server / PostgreSQL など
- 同期は Push(ローカル→クラウド)と Pull(クラウド→ローカル)に分ける
- 差分同期は UpdatedAt / Version で管理
- ローカル変更はアウトボックスに記録して後で送信
- 衝突解決ルールを明文化(Server Wins / Client Wins / UpdatedAt)
- オフライン時もローカルDBだけで業務が回る設計にする
- IDはGUIDなど、クライアント側で一意に発行できる形式にする
まとめ:ローカルDB × クラウド同期は“現場に強い”構成
- ローカルSQLiteで「速さ」と「オフライン」を確保
- クラウドDBで「共有」と「バックアップ」と「集計」を担保
- 同期は Push / Pull / 差分 / 衝突解決 をセットで設計する
「クラウドだけだと現場が不安」「ローカルだけだと共有できない」 そんな現場の悩みに対して、 ローカルDB × クラウド同期(ハイブリッド構成) は非常に強力な解決策になります。 この記事をベースに、あなたの業務アプリに最適な同期戦略を設計してみてください。